package com.study.demo.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.text.InputFilter;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.NumberPicker;

import com.study.demo.R;

import java.lang.reflect.Field;

/**
 * Created by sll on 2016/4/1.
 */
public class MaterialNumberPicker extends NumberPicker {

  private static final int MIN_VALUE = 1;
  private static final int MAX_VALUE = 10;
  private static final int DEFAULT_VALUE = 1;
  private static final float TEXT_SIZE = 20.f;
  private static final int TEXT_COLOR = Color.BLACK;
  private static final int BACKGROUND_COLOR = Color.WHITE;
  private static final int SEPARATOR_COLOR = Color.TRANSPARENT;

  private Builder mBuilder;
  private int mTextColor;
  private float mTextSize;
  private int mSeparatorColor;
  private boolean mEnableFocusability;

  public MaterialNumberPicker(Context context) {
    super(context);
    initView();
  }

  public MaterialNumberPicker(Context context, AttributeSet attributeSet) {
    super(context, attributeSet);
    initView();

    TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.MaterialNumberPicker);

    for (int i = 0; i < a.getIndexCount(); ++i) {

      int attr = a.getIndex(i);
      if (attr == R.styleable.MaterialNumberPicker_npMinValue) {
        setMinValue(a.getInt(attr, MIN_VALUE));
      } else if (attr == R.styleable.MaterialNumberPicker_npMaxValue) {
        setMaxValue(a.getInt(attr, MAX_VALUE));
      } else if (attr == R.styleable.MaterialNumberPicker_npDefaultValue) {
        setValue(a.getInt(attr, DEFAULT_VALUE));
      } else if (attr == R.styleable.MaterialNumberPicker_npTextSize) {
        setTextSize(a.getDimension(attr, TEXT_SIZE));
      } else if (attr == R.styleable.MaterialNumberPicker_npTextColor) {
        setTextColor(a.getColor(attr, TEXT_COLOR));
      } else if (attr == R.styleable.MaterialNumberPicker_npSeparatorColor) {
        setSeparatorColor(a.getColor(attr, SEPARATOR_COLOR));
      } else if (attr == R.styleable.MaterialNumberPicker_npFocusValue) {
        setFocusability(a.getBoolean(attr, false));
      } else if (attr == R.styleable.MaterialNumberPicker_npWrapValue) {
        setWrapSelectorWheel(a.getBoolean(attr, false));
      } else if (attr == R.styleable.MaterialNumberPicker_npBackground) {
        setBackgroundColor(a.getColor(attr, BACKGROUND_COLOR));
      }
    }

    a.recycle();
  }

  public MaterialNumberPicker(Builder builder) {
    super(builder.context);
    initView();

    mBuilder = builder;

    setMinValue(builder.minValue);
    setMaxValue(builder.maxValue);
    setValue(builder.defaultValue);
    setFormatter(builder.formatter);
    setBackgroundColor(builder.backgroundColor);
    setSeparatorColor(builder.separatorColor);
    setTextColor(builder.textColor);
    setTextSize(spToPixels(getContext(), builder.textSize));
    setWrapSelectorWheel(builder.wrapSelectorWheel);
    setFocusability(builder.enableFocusability);
  }

  public final Builder getBuilder() {
    return this.mBuilder;
  }

  public int getTextColor() {
    return mTextColor;
  }

  public int getSeparatorColor() {
    return mSeparatorColor;
  }

  public boolean isFocusabilityEnabled() {
    return mEnableFocusability;
  }

  /**
   * Init number picker by disabling focusability of edit text embedded inside the number picker
   * We also override the edit text filter private attribute by using reflection as the formatter
   * is
   * still buggy while attempting to display the default value
   * This is still an open Google @see <a href="https://code.google.com/p/android/issues/detail?id=35482#c9">issue</a>
   * from 2012
   */
  private void initView() {
    setMinValue(MIN_VALUE);
    setMaxValue(MAX_VALUE);
    setValue(DEFAULT_VALUE);
    setBackgroundColor(BACKGROUND_COLOR);
    setSeparatorColor(SEPARATOR_COLOR);
    setTextColor(TEXT_COLOR);
    setTextSize(TEXT_SIZE);
    setWrapSelectorWheel(false);
    setFocusability(false);

    try {
      Field f = NumberPicker.class.getDeclaredField("mInputText");
      f.setAccessible(true);
      EditText inputText = (EditText) f.get(this);
      inputText.setFilters(new InputFilter[0]);
    } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
      e.printStackTrace();
    }
  }

  /**
   * Uses reflection to access divider private attribute and override its color
   * Use Color.Transparent if you wish to hide them
   */
  public void setSeparatorColor(int separatorColor) {
    mSeparatorColor = separatorColor;

    Field[] pickerFields = NumberPicker.class.getDeclaredFields();
    for (Field pf : pickerFields) {
      if (pf.getName().equals("mSelectionDivider")) {
        pf.setAccessible(true);
        try {
          pf.set(this, new ColorDrawable(separatorColor));
        } catch (IllegalAccessException | IllegalArgumentException e) {
          e.printStackTrace();
        }
        break;
      }
    }
  }

  /**
   * Uses reflection to access text color private attribute for both wheel and edit text inside the
   * number picker.
   */
  public void setTextColor(int textColor) {
    mTextColor = textColor;
    updateTextAttributes();
  }

  /**
   * Uses reflection to access text size private attribute for both wheel and edit text inside the
   * number picker.
   */
  public void setTextSize(float textSize) {
    mTextSize = textSize;
    updateTextAttributes();
  }

  private void updateTextAttributes() {
    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);
      if (child instanceof EditText) {
        try {
          Field selectorWheelPaintField =
              NumberPicker.class.getDeclaredField("mSelectorWheelPaint");
          selectorWheelPaintField.setAccessible(true);

          Paint wheelPaint = ((Paint) selectorWheelPaintField.get(this));
          wheelPaint.setColor(mTextColor);
          wheelPaint.setTextSize(mTextSize);

          EditText editText = ((EditText) child);
          editText.setTextColor(mTextColor);
          editText.setTextSize(pixelsToSp(getContext(), mTextSize));

          invalidate();
          break;
        } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
          e.printStackTrace();
        }
      }
    }
  }

  private void setFocusability(boolean isFocusable) {
    mEnableFocusability = isFocusable;
    setDescendantFocusability(isFocusable ? FOCUS_AFTER_DESCENDANTS : FOCUS_BLOCK_DESCENDANTS);
  }

  private float pixelsToSp(Context context, float px) {
    return px / context.getResources().getDisplayMetrics().scaledDensity;
  }

  private float spToPixels(Context context, float sp) {
    return sp * context.getResources().getDisplayMetrics().scaledDensity;
  }

  public static class Builder {
    private Context context;
    private Formatter formatter;
    private int backgroundColor = BACKGROUND_COLOR;
    private int separatorColor = SEPARATOR_COLOR;
    private int textColor = TEXT_COLOR;
    private float textSize = TEXT_SIZE;
    private int minValue = MIN_VALUE;
    private int maxValue = MAX_VALUE;
    private int defaultValue = DEFAULT_VALUE;
    private boolean enableFocusability = false;
    private boolean wrapSelectorWheel = false;

    public Builder(@NonNull Context context) {
      this.context = context;
    }

    public Builder formatter(Formatter formatter) {
      this.formatter = formatter;
      return this;
    }

    public Builder backgroundColor(int backgroundColor) {
      this.backgroundColor = backgroundColor;
      return this;
    }

    public Builder separatorColor(int separatorColor) {
      this.separatorColor = separatorColor;
      return this;
    }

    public Builder textColor(int textColor) {
      this.textColor = textColor;
      return this;
    }

    public Builder textSize(float textSize) {
      this.textSize = textSize;
      return this;
    }

    public Builder minValue(int minValue) {
      this.minValue = minValue;
      return this;
    }

    public Builder maxValue(int maxValue) {
      this.maxValue = maxValue;
      return this;
    }

    public Builder defaultValue(int defaultValue) {
      this.defaultValue = defaultValue;
      return this;
    }

    public Builder wrapSelectorWheel(boolean wrapSelectorWheel) {
      this.wrapSelectorWheel = wrapSelectorWheel;
      return this;
    }

    public Builder enableFocusability(boolean enableFocusability) {
      this.enableFocusability = enableFocusability;
      return this;
    }

    public MaterialNumberPicker build() {
      return new MaterialNumberPicker(this);
    }
  }
}
